namespace ScrumSprintMonitor.ServerAdapter.TFS
{
    #region #using Directives

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Linq;
    using System.Security;
    using System.Threading;

    using Infrastructure;
    using Infrastructure.Configuration;

    using Isolation.TeamFoundationServer;

    using Microsoft.Practices.Unity;

    #endregion

    public class TfsConnection : IConnection
    {
        private readonly IUnityContainer container;
        private readonly ILogger<TfsConnection> logger;
        private readonly string password;
        private readonly IProcessTemplateAdapterProvider processTemplateAdapterProvider;
        private readonly Uri serverUri;
        private readonly object syncObject = new object();
        private readonly Mutex tfsLock = new Mutex();
        private readonly string userName;
        private ITfsBuildServer buildServer;
        private bool disposed;
        private ITeamFoundationServer tfs;
        private ITfsWorkItemStore workItemStore;

        public TfsConnection(IUnityContainer container, Uri serverUri, string userName, string password,
                             IProcessTemplateAdapterProvider processTemplateAdapterProvider, ILogger<TfsConnection> logger)
        {
            if (serverUri == null)
            {
                throw new ArgumentNullException("serverUri");
            }
            if (processTemplateAdapterProvider == null)
            {
                throw new ArgumentNullException("processTemplateAdapterProvider");
            }

            this.container = container;
            this.serverUri = serverUri;
            this.userName = userName;
            this.password = password;
            this.processTemplateAdapterProvider = processTemplateAdapterProvider;
            this.logger = logger;
        }

        #region IConnection Members

        public bool IsDisposed
        {
            [DebuggerStepThrough]
            get { return this.disposed; }
        }

        public void Connect()
        {
            GuardDisposed();

            if (IsConnected)
            {
                throw new InvalidOperationException("The connection to the server is already established.");
            }

            lock (this.syncObject)
            {
                if (!IsConnected)
                {
                    this.logger.Debug("Connecting to TFS...");

                    if (this.tfs == null)
                    {
                        Interlocked.Exchange(ref this.tfs, new TeamFoundationServer(this.serverUri, this.userName, this.password));
                    }

                    Interlocked.Exchange(ref this.buildServer, this.tfs.GetBuildServer());
                    Interlocked.Exchange(ref this.workItemStore, this.tfs.GetWorkItemStore());

                    if (IsConnected)
                    {
                        OnPropertyChanged("IsConnected");
                    }
                }
            }
        }

        public void Disconnect()
        {
            lock (this.syncObject)
            {
                if (this.tfs != null)
                {
                    this.logger.Debug("Disconnecting from TFS.");

                    this.buildServer = null;
                    this.workItemStore = null;
                    this.tfs.Dispose();
                    this.tfs = null;
                }

                OnPropertyChanged("IsConnected");
            }
        }

        public IBuildInfo GetBuildInfo(ISprintConfiguration configuration)
        {
            GuardDisposed();

            if (configuration == null)
            {
                throw new ArgumentNullException("configuration");
            }
            if (this.buildServer == null)
            {
                return null;
            }

            this.logger.Debug("Getting build info.");

            var parameters = new[] {new ParameterOverride("buildServer", this.buildServer), new ParameterOverride("tfsLock", this.tfsLock)};
            return this.container.Resolve<IBuildInfo>(parameters);
        }

        public string GetSprintName(ISprintConfiguration configuration)
        {
            GuardDisposed();

            if (configuration == null)
            {
                throw new ArgumentNullException("configuration");
            }

            try
            {
                if (string.IsNullOrEmpty(configuration.IterationPath))
                {
                    return this.workItemStore.GetIterationName(configuration.ProjectName, configuration.IterationId);
                }

                return this.workItemStore.GetIterationName(configuration.ProjectName, configuration.IterationPath);
            }
            catch (TfsConnectionException e)
            {
                throw new ConnectionException(e);
            }
        }

        public Uri GetWorkItemDisplayUri(IWorkItemAdapter workItem)
        {
            GuardDisposed();

            this.logger.Debug("Getting work item display Uri for work item #{0}.", workItem.Id);

            return new Uri(this.tfs.Uri, string.Format("WorkItemTracking/WorkItem.aspx?artifactMoniker={0}", workItem.Id));
        }

        public IWorkItemStore GetWorkItemStore(ISprintConfiguration configuration)
        {
            GuardDisposed();

            if (configuration == null)
            {
                throw new ArgumentNullException("configuration");
            }
            if (!IsConnected)
            {
                throw new ConnectionException("The connection is not yet established");
            }

            var processTemplateAdapter = GetProcessTemplate(configuration);
            string tfsQueryString;

            if (string.IsNullOrEmpty(configuration.IterationPath))
            {
                this.logger.Debug("Getting work item store for {0}...", configuration.IterationId);

                tfsQueryString = this.workItemStore.GetQueryString(configuration.ProjectName, configuration.IterationId,
                                                                   GetWorkItemTypesQueryString(processTemplateAdapter.WorkItemTypeNames));
            }
            else
            {
                this.logger.Debug("Getting work item store for {0}...", configuration.IterationPath);

                tfsQueryString = this.workItemStore.GetQueryString(configuration.ProjectName, configuration.IterationPath,
                                                                   GetWorkItemTypesQueryString(processTemplateAdapter.WorkItemTypeNames));
            }

            var parameters = new[]
                                 {
                                     new ParameterOverride("tfsWorkItemStore", this.workItemStore),
                                     new ParameterOverride("processTemplateAdapter", processTemplateAdapter),
                                     new ParameterOverride("tfsQueryString", tfsQueryString), new ParameterOverride("tfsLock", this.tfsLock)
                                 };
            return this.container.Resolve<IWorkItemStore>(parameters);
        }

        public ICollection<string> GetProjectList()
        {
            GuardDisposed();

            if (!IsConnected)
            {
                Connect();
            }

            this.logger.Debug("Getting project list...");

            return this.workItemStore.Projects;
        }

        public ICollection<string> GetBuildDefinitionList(string teamProject)
        {
            if (!IsConnected)
            {
                Connect();
            }

            this.logger.Debug("Getting build definition list for team project {0}.", teamProject);

            if (this.workItemStore.Projects.Contains(teamProject))
            {
                var buildDefinitions = this.buildServer.QueryBuildDefinitions(teamProject);

                return buildDefinitions;
            }

            this.logger.Debug("No project exists with name {0}.", teamProject);

            return null;
        }

        public ICollection<string> GetIterationPaths(string teamProject)
        {
            GuardDisposed();

            if (!IsConnected)
            {
                Connect();
            }

            this.logger.Debug("Getting iteration paths list for team project {0}.", teamProject);

            if (!this.workItemStore.Projects.Contains(teamProject))
            {
                this.logger.Debug("No project exists with name {0}.", teamProject);

                return null;
            }

            try
            {
                return this.tfs.GetIterationPaths(teamProject);
            }
            catch (SecurityException e)
            {
                this.logger.Info(e, "Security exception found while querying TFS for iteration paths.");

                return new List<string>();
            }
        }

        public bool IsConnected
        {
            [DebuggerStepThrough]
            get
            {
                GuardDisposed();

                return this.workItemStore != null;
            }
        }

        public void Dispose()
        {
            if (!this.disposed)
            {
                this.disposed = true;

                this.tfsLock.Close();

                Disconnect();
            }
        }

        #endregion

        [DebuggerStepThrough]
        private void GuardDisposed()
        {
            if (this.disposed)
            {
                throw new ObjectDisposedException("TfsConnection");
            }
        }

        private IProcessTemplateAdapter GetProcessTemplate(ISprintConfiguration configuration)
        {
            try
            {
                return
                    this.processTemplateAdapterProvider.AvailableProcessTemplates.First(
                        x => x.ProcessTemplateName == configuration.ProcessTemplateName);
            }
            catch (InvalidOperationException e)
            {
                throw new ArgumentException(string.Format("'{0}' is not a recognized process template name.", configuration.ProcessTemplateName),
                                            "configuration", e);
            }
        }

        private static string GetWorkItemTypesQueryString(IEnumerable<string> workItemTypeNames)
        {
            return string.Join(", ", workItemTypeNames.Select(x => string.Format("'{0}'", x)));
        }

        #region INotifyPropertyChanged members

        public event PropertyChangedEventHandler PropertyChanged;

        public void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion
    }
}
